#!/usr/bin/env python
# -*-coding:utf-8 -*-
# Project_name:XmidToExcelAndImportToTAPD
# File_name:xmid_to_excel_and_import_to_TAPD_window_500_reverse.py
# Author:chen chang song
# Time:2025年08月14日
import os
import re
import time
import json
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import xmindparser
from openpyxl import Workbook
from openpyxl.styles import Alignment, PatternFill
from openpyxl.formatting.rule import CellIsRule
from openpyxl.worksheet.datavalidation import DataValidation


class KmToExcelConverterGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("XMind转Excel工具_顺序反转_V20250814")
        self.mind_path = ""

        # 主框架
        self.main_frame = ttk.Frame(root, padding="10")
        self.main_frame.pack(fill=tk.BOTH, expand=True)

        # 标题栏
        self.header_frame = ttk.Frame(self.main_frame)
        self.header_frame.pack(fill=tk.X)

        # 帮助按钮
        self.help_btn = ttk.Button(
            self.header_frame,
            text="帮助?",
            width=6,
            command=self.show_help
        )
        self.help_btn.pack(side=tk.RIGHT, padx=5)


        # 文件路径选择部分
        self.path_frame = ttk.LabelFrame(self.main_frame, text="XMind文件路径：", padding="10")
        self.path_frame.pack(fill=tk.X, pady=5)

        self.path_label = ttk.Label(self.path_frame, text="未选择路径")
        self.path_label.pack(side=tk.LEFT, fill=tk.X, expand=True)

        self.select_btn = ttk.Button(
            self.path_frame,
            text="选择xmind文件路径",
            command=self.select_directory
        )
        self.select_btn.pack(side=tk.LEFT, padx=5)

        self.convert_btn = ttk.Button(
            self.path_frame,
            text="开始转换",
            state=tk.DISABLED,
            command=self.start_conversion
        )
        self.convert_btn.pack(side=tk.LEFT)

        # 转换结果部分
        self.result_frame = ttk.LabelFrame(self.main_frame, text="转换结果:", padding="10")
        self.result_frame.pack(fill=tk.BOTH, expand=True, pady=5)

        self.result_text = tk.Text(
            self.result_frame,
            height=7,  # 原为5，增加了三分之一(5*1.33≈7)
            wrap=tk.WORD,
            state=tk.DISABLED
        )
        self.result_text.pack(fill=tk.BOTH, expand=True)

        # 状态栏
        self.status_var = tk.StringVar()
        self.status_bar = ttk.Label(
            root,
            textvariable=self.status_var,
            relief=tk.SUNKEN,
            anchor=tk.W
        )
        self.status_bar.pack(fill=tk.X)

        self.status_var.set("就绪")

    def select_directory(self):
        """选择xmind文件目录"""
        path = filedialog.askdirectory(title="选择XMind文件所在目录")
        if path:
            self.mind_path = path
            self.path_label.config(text=path)
            self.convert_btn.config(state=tk.NORMAL)
            self.status_var.set(f"已选择目录: {path}")

    def start_conversion(self):
        """开始转换处理"""
        if not self.mind_path:
            messagebox.showerror("错误", "请先选择XMind文件路径")
            return

        self.status_var.set("正在转换中...")
        self.root.update()

        try:
            converter = KmToExcelConverter(self.mind_path)
            test_cases, excel_files = converter.all_xmind_path_list()

            # 构建结果消息
            result_msg = f"共转换 {len(test_cases)} 个xmind文件\n\n生成的Excel文件:\n"
            result_msg += "\n".join(f"• {file}" for file in excel_files)
            result_msg += f"\n\n请到 {self.mind_path} 路径下查看！"

            self.update_result(result_msg)
            self.status_var.set("转换完成")

        except Exception as e:
            messagebox.showerror("转换错误", f"转换过程中出现错误:\n{str(e)}")
            self.status_var.set("转换失败")

    def update_result(self, message):
        """更新结果文本框"""
        self.result_text.config(state=tk.NORMAL)
        self.result_text.delete(1.0, tk.END)
        self.result_text.insert(tk.END, message)
        self.result_text.config(state=tk.DISABLED)

    def show_help(self):
        """显示帮助信息"""
        help_msg = "如需帮助请联系昌松"
        messagebox.showinfo("帮助", help_msg)


class KmToExcelConverter:
    def __init__(self, mind_path):
        """初始化转换器类
        :param mind_path: xmind文件存放目录路径
        """
        self.mind_path = mind_path

    def all_xmind_path_list(self):
        """遍历目录获取所有xmind文件，获取所有节点内容，写入到excel
        :return: (解析后的数据列表, 生成的Excel文件名列表)
        """
        file_name_list = []
        xmind_data_list = []
        excel_files = []
        for filename in os.listdir(self.mind_path):
            if filename.endswith('.xmind'):
                file_name_list.append(filename)
                xmind_path = os.path.join(self.mind_path, filename)
                xmind_data = self.extract_xmind_content(xmind_path)
                xmind_data_list.append(xmind_data)
                excel_file = self.test_case_write_to_excel(xmind_path, xmind_data)
                excel_files.append(excel_file)
        return xmind_data_list, excel_files

    def extract_xmind_content(self, file_path):
        """解析xmind文件内容
        :param file_path: xmind文件路径
        :return: 提取的结构化测试用例数据
        """
        xmind_content = xmindparser.xmind_to_dict(file_path)
        # 提取 topic 下的 title 字段
        json_str = xmind_content[0]['topic']['title']
        # 需求id
        if '需求ID' in json_str:
            pattern = r'需求ID[：:]\s*(\d+)'
            match = re.search(pattern, json_str)
            requirement_id = match.group(1) if match else None
            print(f'需求ID：{requirement_id}')
        elif 'requirement_id' in json_str:
            data_list = json.loads(json_str)
            requirement_id = data_list['requirement_id']
            print(f'需求ID：{requirement_id}')
        else:
            requirement_id = ''
            print('需求ID格式不正确,请手动填写')
        results = []  # 存储提取结果

        def traverse(node, current_path, module_name):
            """递归遍历xmind节点
            :param node: 当前节点
            :param current_path: 当前路径列表
            :param module_name: 所属模块名
            """
            if 'topics' in node:
                for topic in node['topics']:
                    new_path = current_path + [topic['title']]
                    if 'topics' in topic:
                        traverse(topic, new_path, module_name)
                    else:
                        # 提取叶子节点作为测试用例[需求ID,用例目录,用例名称,用例步骤,预期结果]
                        case_directory = module_name + "-" + "-".join(new_path[:-2]) if len(new_path) > 1 else ""
                        case_name = new_path[-2] + "-" + new_path[-1]
                        case_steps = " >> ".join(new_path[:-1]) if len(new_path) > 1 else ""
                        one_case = [requirement_id, case_directory, case_name, case_steps, new_path[-1]]
                        print(one_case)
                        results.append(one_case)
        # 处理每个模块
        for module in xmind_content[0]['topic']['topics']:
            module_name = module['title']
            if 'topics' in module:
                for func in module['topics']:
                    traverse(func, [func['title']], module_name)

        return results

    def test_case_write_to_excel(self, xmind_path, row_data_list):
        """将测试用例数据写入Excel文件
        :param xmind_path: 原始xmind文件路径（用于生成输出文件名）
        :param row_data_list: 测试用例数据列表
        """
        # ============== 文件准备 ==============
        file_dir, file_name = os.path.split(xmind_path)
        file_base = os.path.splitext(file_name)[0]
        timestamp = time.strftime("%Y%m%d%H%M%S")

        row_data_list1 = list(reversed(row_data_list))#TAPD导入顺序乱，反转下
        # 计算需要拆分的文件数量 (每500条数据一个文件)
        total_cases = len(row_data_list1)
        # 修复文件数量计算逻辑
        num_files = (total_cases - 1) // 500 + 1  # 正确的向上取整方法

        print(f"总用例数: {total_cases}, 需要拆分成 {num_files} 个文件")  # 调试信息

        # 文件列表
        files_list = []
        # ============== 处理每个文件分片 ==============
        for file_index in range(num_files):
            # 计算当前分片的数据范围
            start_index = file_index * 500
            end_index = min((file_index + 1) * 500, total_cases)
            current_data = row_data_list1[start_index:end_index]

            print(f"处理分片 {file_index + 1}: {len(current_data)}条数据")  # 调试信息

            wb = Workbook()
            ws = wb.active
            ws.title = "测试用例"  # 设置工作表名称

            # ============== 表头设置 ==============
            title_list = ['需求ID', '用例目录', '用例名称', '用例步骤', '预期结果', '执行结果', '备注']
            ws.append(title_list)
            ws.auto_filter.ref = "A1:G1"  # 设置自动筛选

            # ============== 列宽设置 ==============
            col_config = {
                'A': 8, 'B': 40, 'C': 25,
                'D': 80, 'E': 20, 'F': 10, 'G': 25
            }
            for col, width in col_config.items():
                ws.column_dimensions[col].width = width

            # ============== 数据验证设置 ==============
            status_options = ["通过", "失败", "阻塞", "未执行", "待确认", "用例待更新", "用例废弃"]
            dv = DataValidation(
                type="list",
                formula1=f'"{",".join(status_options)}"',
                allow_blank=True
            )
            ws.add_data_validation(dv)
            # 应用范围根据当前分片数据量计算
            if current_data:  # 确保有数据时才添加验证
                dv_range = f"F2:F{len(current_data) + 1}"
                dv.add(dv_range)

            # ============== 条件格式设置 ==============
            status_colors = {
                "通过": '00FF00',  # 绿色
                "失败": 'FF7F00',  # 橙色
                "阻塞": 'FF0000',  # 红色
                "未执行": 'FFFF00',  # 黄色
                "待确认": 'FFFF00',
                "用例待更新": 'FFFF00',
                "用例废弃": 'FFFF00'
            }
            if current_data:  # 确保有数据时才添加格式
                for status, color in status_colors.items():
                    rule = CellIsRule(
                        operator='equal',
                        formula=[f'"{status}"'],
                        fill=PatternFill(
                            start_color=color,
                            end_color=color,
                            fill_type='solid'
                        )
                    )
                    # 应用范围根据当前分片数据量计算
                    cf_range = f'F2:F{len(current_data) + 1}'
                    ws.conditional_formatting.add(cf_range, rule)

            # ============== 写入测试数据 ==============
            for case_data in current_data:
                ws.append(case_data)

            # ============== 格式优化 ==============
            ws.freeze_panes = 'A2'  # 冻结首行

            # 设置所有单元格自动换行
            for row in ws.iter_rows(min_row=1):
                for cell in row:
                    cell.alignment = Alignment(wrap_text=True, vertical='top')

            # 设置行高
            ws.row_dimensions[1].height = 20  # 标题行
            for row_idx in range(2, len(current_data) + 2):
                ws.row_dimensions[row_idx].height = 32  # 数据行

            # ============== 文件保存 ==============
            # 添加分片序号到文件名（当有多个文件时）
            if num_files > 1:
                suffix = f"_顺序反转_{timestamp}_{file_index + 1}"
            else:
                suffix = f"_顺序反转_{timestamp}"

            output_path = os.path.join(file_dir, f"{file_base}{suffix}.xlsx")

            # 确保输出目录存在
            os.makedirs(file_dir, exist_ok=True)

            try:
                wb.save(output_path)
                print(f"成功生成文件: {output_path} ({len(current_data)}条用例)")
                files_list.append(output_path)
            except PermissionError:
                print(f"权限错误: 无法写入文件 {output_path}，可能文件正在被其他程序打开")
            except Exception as e:
                print(f"保存文件 {output_path} 时出错: {str(e)}")

        print(f"总计生成 {num_files} 个文件，共处理 {total_cases} 条测试用例")
        return files_list

if __name__ == "__main__":
    root = tk.Tk()
    app = KmToExcelConverterGUI(root)
    root.mainloop()